package org.xx.armory.spring5.mvc.captchacode;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.util.Pair;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.IOException;
import java.util.Random;

import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;

public class DefaultCaptchaCodeImageProvider
        implements CaptchaCodeImageProvider {
    private static final AffineTransform IDENTITY_TRANS = new AffineTransform();
    private static final FontRenderContext PRIMARY_CONTEXT = new FontRenderContext(IDENTITY_TRANS, true, false);
    private static final Font PRIMARY_FONT = new Font(Font.SERIF, Font.BOLD, 48);

    /**
     * <strong>不使用下列字符，因为它们变形后不易辨认 'l', 'L', 'O', 'o', '0' etc... </strong>
     */
    private static final char[] CODE_SET = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', '3', '4', '6', '7', '8', '9'};

    @Value("${armory.mvc.captcha-code-image.code-length:4}")
    private int codeLength;

    @Value("${armory.mvc.captcha-code-image.width:64}")
    private int width;

    @Value("${armory.mvc.captcha-code-image.height:24}")
    private int height;

    /**
     * 生成一个线性的渐变矩阵。
     * <p>该矩阵用于以扭曲的方式绘制图形</p>
     *
     * @param w1
     *         原始图形宽度。
     * @param h1
     *         原始图形高度。
     * @param w2
     *         扭曲后的目标宽度。
     * @param h2
     *         扭曲后的目标高度。
     * @return 渐变矩阵。
     * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform)
     */
    private static AffineTransform transform(
            double w1,
            double h1,
            double w2,
            double h2
    ) {
        final var SHEARING_K = 0.1D; /* It should between 0D (exclusive) and 1D (exclusive) */

        final var rnd = new Random();
        final var shift = (rnd.nextInt() & 0x01) == 0;
        final var shearingX = SHEARING_K * w2 / h2;
        return new AffineTransform(w2 * (1D - SHEARING_K) / w1, 0D, shift ? shearingX : -shearingX,
                                   h2 / h1, shift ? 0D : w2 * SHEARING_K, 0D);
    }

    @Override
    public Pair<String, BufferedImage> createCodeAndImage()
            throws IOException {
        final var rnd = new Random();

        // 生成随机码。
        final var captchaCodeBuilder = new StringBuilder();
        for (var i = 0; i < this.codeLength; ++i) {
            captchaCodeBuilder.append(CODE_SET[rnd.nextInt(CODE_SET.length)]);
        }
        final var captchaCode = captchaCodeBuilder.toString();

        // 绘制随机码。
        final var lineY1 = rnd.nextDouble() * .4 + .3;

        // Prepare regular image
        final var rect1 = PRIMARY_FONT.getStringBounds(captchaCode, PRIMARY_CONTEXT);
        final var imageCode = new BufferedImage((int) rect1.getWidth(), (int) rect1.getHeight(), BufferedImage.TYPE_INT_RGB);
        final var gCode = imageCode.createGraphics();
        try {
            gCode.setColor(Color.WHITE);
            gCode.fillRect(0, 0, (int) rect1.getWidth(), (int) rect1.getHeight());
            gCode.setColor(Color.BLUE);
            // Draw code text
            gCode.setFont(PRIMARY_FONT);
            gCode.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
            final var baseY = -rect1.getY();
            gCode.drawString(captchaCode, 0, (int) baseY);
            // Draw disturb line
            final int x2 = (int) rect1.getWidth();
            final int y1 = (int) (rect1.getHeight() * lineY1);
            final int y2 = (int) (rect1.getHeight() * (1 - lineY1));
            gCode.drawLine(0, y1, x2, y2);
            gCode.drawLine(0, y1 - 1, x2, y2 - 1);
            gCode.drawLine(0, y1 + 1, x2, y2 + 1);
        } finally {
            gCode.dispose();
        }

        // Map to destination image
        final var image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        final var g = image.createGraphics();
        try {
            g.setColor(new Color(0xFF, 0xFF, 0xFF, 0x0));
            g.fillRect(0, 0, width, height);
            g.drawRenderedImage(imageCode, transform(rect1.getWidth(), rect1.getHeight(), width, height));
        } finally {
            g.dispose();
        }

        return Pair.of(captchaCode, image);
    }
}
